package controller;

import java.awt.Point;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import view.DisplayManager;

import model.Board;
import model.BoardManager;
import model.Model;

import model.pieces.King;
import model.pieces.Knight;
import model.pieces.Pawn;
import model.pieces.Piece;

/**
 * Class manages game's logic
 */
public class GameManager implements Controller, HumanControlledGame, ComputerControlledGame {
	
	// members
	
	// constants
	private static final int MAX_TURNS = 130; // maximal number of turns in automatic game
	private static final int MAX_PIECES = 16; // maximal number of pieces for a player on partial game
	private static final int CHANCE_FOR_CREATURE = 2; // probability to add a Creature at a certain position	
	private static final String[] REPLACING_PIECES = {"Queen", "Rook", "Bishop", "Knight"}; // options for piece to replace a Pawn that reached board's end
	private static final int DEFAULT_REPLACING_PIECE = 0; // default piece option to replace Pawn

	// non-constants
	private Model boardManager; // manages chess game board
	private Random randomGenerator; // uses to generate random numbers
	private int boardSize; // game board size 
	private boolean isFirstPlayerTurn; // indicates if first player should play at current turn	
	private List<Point> creaturePositions; // positions of Creatures on board
	private DisplayManager displayManager; // used to manage game display
	private ComputerPlayer computerPlayer; // used to perform computer's moves
	private Hazards hazardChecker; // used to check for game hazards
	
	// TODO DEBUG!!!
	public Logger logger;
	//
	
	// private methods
	
	/**
	 * Chooses a piece from indicated player's assigned pieces. If chosen piece cannot move, chooses
	 * another one. Once chosen piece can move, chooses a move and returns it
	 */		
	private Move chooseMove() {

		Point srcPosition = null; // position of piece to be moved
		List<Point> moves = null; // moves of chosen piece
		
		// randomly choose a piece that can move
		boolean cannotMove = true;
		while (cannotMove) {
		
			// find source position
			srcPosition = computerPlayer.getPieceComputer(isFirstPlayerTurn);
			
			// get moves of chosen piece
			moves = getNextMove(srcPosition);
			
			// check if piece at chosen position can move			
			if (!moves.isEmpty()) {
				
				// chosen piece can move, done looping
				cannotMove = false;
			}
		}
		
		// find destination position
		Point destPosition = computerPlayer.getMoveComputer(moves);
		
		// return move chosen by user
		return new Move(srcPosition, destPosition);
	}
	
	/**
	 * Gets two positions and returns all positions that lay on the path between them 
	 * @param srcPosition- path's source position
	 * @param destPosition- path's destination position
	 * @return all points that lay on the path
	 */
	private List<Point> getPath(Point srcPosition, Point destPosition) {
		 
		// allocate list to keep all positions on path
		List<Point> path = new LinkedList<Point>();
		 
		// find difference in row and column
		int rowDiff = destPosition.x - srcPosition.x;
		int colDiff = destPosition.y - srcPosition.y;
		 
		// find all positions on path
		Point currPosition = new Point(srcPosition);
		 		 		 
		// initialize step size on each axis
		int rowStep = 0;
		int colStep = 0;
		 
		// get row difference sign
		if (rowDiff != 0) {
			rowStep = rowDiff / Math.abs(rowDiff);			 
		}
		 
		// get column difference sign
		if (colDiff != 0) {			 
			colStep = colDiff / Math.abs(colDiff);
		}
		 
		// scan path from source to destination
		while (!currPosition.equals(destPosition)) {
			 
			// check if can move on row
			if (currPosition.x != destPosition.x) {
				
				// move on row
				currPosition.translate(rowStep, 0);
			}
				 
			// check if can move on column
			if (currPosition.y != destPosition.y) {
				
				// move on column
				currPosition.translate(0, colStep);
			}
			 
			// add current position on path
			path.add(new Point(currPosition));
		}
		 
		// found all positions on path
		return path;
	}
	
	/**
	 * Gets two positions and returns all positions that lay on the path between them.
	 * Calculates the path according to Knight's move 
	 * @param srcPosition- path's source position
	 * @param destPosition- path's destination position
	 * @return all points that lay on the path
	 */
	private List<Point> getPathKnight(Point srcPosition, Point destPosition) {
		 
		// allocate list to keep all positions on path
		List<Point> path = new LinkedList<Point>();
		 
		// find difference in row and column
		int rowDiff = destPosition.x - srcPosition.x;
		int colDiff = destPosition.y - srcPosition.y;
		
		// check if should move 2 steps on row axis
		if (Math.abs(rowDiff) == 2) {

			// calculate step sign
			int rowStep = rowDiff / Math.abs(rowDiff);
			 
			// add positions on path
			path.add(new Point(srcPosition.x + rowStep, srcPosition.y));
			path.add(new Point(srcPosition.x + 2*rowStep, srcPosition.y));
			path.add(new Point(destPosition));
			 
		} else { // move 2 steps on column axis

			// calculate step sign
			int colStep = colDiff / Math.abs(colDiff);
			 
			// add positions on path
			path.add(new Point(srcPosition.x, srcPosition.y + colStep));
			path.add(new Point(srcPosition.x, srcPosition.y + 2*colStep));
			path.add(new Point(destPosition));
		}
		 
		// found all positions on path 
		return path;
	 }
	
	/**
	 * Gets Knight's move and positions on the path that the Knight moves on,
	 * and destroys all Creatures that lay on the path
	 * @param path- positions on Knight's path
	 * @param move- Knight's move
	 */
	private void moveKnightSpecialGame(List<Point> path, Move move) {
		 
		// get path
		path.addAll(getPathKnight(move.getSource(), move.getDestination()));
		 
		// check if passes through Creatures			 
		for (Point currPosition : path) {
			if (creaturePositions.contains(currPosition)) {
				// destroy Creature
				creaturePositions.remove(currPosition);
			}
		}
		 
		// make move
		boardManager.movePiece(isFirstPlayerTurn, move.getSource(), move.getDestination());
	}
	
	/**
	 * Gets King's move and positions on the path that the King moves on,
	 * and destroys all Creatures that lay on the path
	 * @param path- positions on King's path
	 * @param move- King's move
	 */
	private void moveKingSpecialGame(List<Point> path, Move move) {

		// get path
		path.addAll(getPath(move.getSource(), move.getDestination()));
		 
		// check if passes through Creatures			 
		for (Point currPosition : path) {
			if (creaturePositions.contains(currPosition)) {
				// destroy Creature
				creaturePositions.remove(currPosition);
			}
		}
		 
		// make move
		boardManager.movePiece(isFirstPlayerTurn, move.getSource(), move.getDestination());
	}
	
	// public methods
	
	/**
	 * Constructor- creates a new game manager
	 */
	public GameManager() {
		
		// allocate a new game board
		boardManager = new BoardManager();
		
		// allocate a new random number generator
//		randomGenerator = new Random(1362065412502);
//		randomGenerator = new Random(System.currentTimeMillis());
//		randomGenerator = new Random(912849814);
//		randomGenerator = new Random(1362065412502L);
		long seed = System.currentTimeMillis();
		
		randomGenerator = new Random(seed);
		
		
		// TODO DEBUG!!!
//		long seed = System.currentTimeMillis();
		//
		
		// set board size
		boardSize = boardManager.getBoardSize();
		
		// first player plays at first turn
		isFirstPlayerTurn = true;
		
		// initialize Creature positions list
		creaturePositions = new LinkedList<Point>();
		
		// display manager is set before game starts
		this.displayManager = null;
		
		// allocate computer player
		computerPlayer = new RandomMoves(boardManager, randomGenerator);
		
		// allocate object to check for game hazards
		hazardChecker = new GameTerminator(boardManager, this);
		
		// TODO DEBUG!!!
		try {
			logger = new Logger();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		// write seed
		logger.writeSeed(seed);
		//
	}
	
	/**
	 * Returns the game board
	 */
	@Override
	public Board getGameBoard() { return boardManager.getBoard(); }

	/**
	 * Use the board manager to add piece with given details to the board
	 */
	@Override
	public boolean addPiece(Point position, String type, boolean isFirstPlayer) {
		
		// add piece with given details to the board
		return boardManager.addPiece(position, type, isFirstPlayer); 
	}
	
	/**
	 * Clear the game board, using the board manager.
	 * Initialize first player indicator to first player	
	 */
	public void clearBoard() { 
		 
		// clear board
		boardManager.clearBoard();
		 
		// initialize current player indicator
		isFirstPlayerTurn = true;
	}
	
	/**
	 * Sets game board with standard chess layout and initialize current player indicator
	 */
	@Override
	public void setTowPlayersBoard() {
		
		// set board
		boardManager.setBoardStandard();
		
		// initialize first player
		isFirstPlayerTurn = true;
	}

	/**
	 * Returns maximal turns for computer-controlled game 
	 */
	@Override
	public int getMaxTurns() { return MAX_TURNS; }
	
	/**
	 * Returns maximal pieces for each player in partial game mode 
	 */
	@Override
	public int getMaxPiecesPartialGame() { return MAX_PIECES; }
	
	/**
	 * Sets the display manager object to work with during the game
	 */
	@Override
	public void setDisplayManager(DisplayManager displayManager) { this.displayManager = displayManager; }
	
	/**
	 * Returns game manager as Human-Controlled-Game interface
	 */
	public HumanControlledGame getHumanControlledGameManager() { return this; }

	/**
	 * Returns game manager as Computer-Controlled-Game interface
	 */
	public ComputerControlledGame getComputerControlledGameManager() { return this; }
		
	/**
	 * Returns list of possible moves of piece at given position.
	 * If no piece at given position or if piece at given position is not assigned to currently playing
  	 * player, an empty list is returned.
  	 * Otherwise, gets move list of the piece and returns only the moves that do not result in friendly
  	 * King being threatened
	 */
	@Override
	public List<Point> getNextMove(Point srcPosition) {
		
		// initialize move list
		List<Point> moveList = new LinkedList<Point>();
		
		// check if there is a piece at given position
		if (boardManager.getPiece(srcPosition.x, srcPosition.y) == null) {
			
			// no piece at given position
			return moveList;
		}
		
		// check if piece at given position is assigned to current player
		if (boardManager.getPiece(srcPosition.x, srcPosition.y).getPlayer() != isFirstPlayerTurn) {
			
			// piece at given position cannot move at current turn
			return moveList;
		}
		
		// piece can move at current turn, get its moves
		moveList.addAll(boardManager.getPiece(srcPosition.x, srcPosition.y).getNextMove(boardManager.getBoard(), srcPosition));
		
		// remove moves that result in check
		Iterator<Point> iter = moveList.iterator();
		while (iter.hasNext()) {
			
			// get current move
			Point currMove = new Point(iter.next());
			
			// check if current move is safe
			if (hazardChecker.moveNotSafe(srcPosition, currMove, isFirstPlayerTurn)) {
				
				// current move is not safe, remove from list
				iter.remove();
			}
		}
		
		// return all safe moves left
		return moveList;
	}
	
	/**
	 * Moves piece at source position to destination position.
	 * Confirms that piece at source can move to destination. If not, move is not done and returns False.
	 * Otherwise, move is done and returns True
	 */
	@Override
	public boolean makeMoveHuman(Point srcPosition, Point destPosition) {
		
		// get possible moves from source position
		List<Point> moveList = getNextMove(srcPosition);
		
		// check if can move to destination position
		if (!moveList.contains(destPosition)) {
			
			// cannot move to destination
			return false;
		}
		
		// move piece at source position to destination
		boolean canMove = boardManager.movePiece(isFirstPlayerTurn, srcPosition, destPosition);
		
		// check if could move
		if (!canMove) {
			
			// could not move
			return false;
		}
		
		// TODO DEBUG!!!
		// write to log that current piece is destroyed
		logger.writeMoveHuman(srcPosition, destPosition);
		// print log to screen
		System.out.println();
		System.out.println("log: " + "human move: " + srcPosition.x + "," + srcPosition.y + "," + " to " + destPosition.x + "," + destPosition.y);
		System.out.println();
		//
		
		// check if moving a Pawn
		Piece movedPiece = boardManager.getPiece(destPosition.x, destPosition.y); 
		if (movedPiece instanceof Pawn) {
			
			// update that first move has already been made
			((Pawn)(movedPiece)).updateFirstMove();		
			
			// check if Pawn has reached board end
			if (boardManager.isPieceAtEnd(movedPiece, destPosition)) {
	
				// refresh board display
				displayManager.refreshBoardDisplay();
				
				// get player's choice for piece
				int choice = displayManager.replacePawn(REPLACING_PIECES, DEFAULT_REPLACING_PIECE);
				
				// check if choice is valid
				String pieceType = null;
				if (0 <= choice && choice < REPLACING_PIECES.length) {
					
					// choice is valid, follow user's choice
					pieceType = REPLACING_PIECES[choice];
					
				} else {
					
					// invalid choice, keep the Pawn at its position
					pieceType = "Pawn";
				}
				
				// remove Pawn
				boardManager.removePiece(destPosition, isFirstPlayerTurn);
				
				// replace with the specified piece
				boardManager.addPiece(destPosition, pieceType, isFirstPlayerTurn);
			}
		}
		
		// switch players
		isFirstPlayerTurn = !isFirstPlayerTurn;
		
		// move has been made successfully
		return true;
	}
	
	/**
	 * Chooses a piece and a position the piece can move to, and make the move.
	 */
	@Override
	 public Move makeMoveComputer() {

		// choose a move to make
		Move moveToMake = chooseMove();
		
		// make chosen move
		boardManager.movePiece(isFirstPlayerTurn, moveToMake.getSource(), moveToMake.getDestination());

		// TODO DEBUG!!!
		// write to log that current piece is destroyed
		logger.writeMoveComputer(moveToMake.getSource(), moveToMake.getDestination());
		// print log to screen
		System.out.println();
		System.out.println("log: " + "computer move: " + moveToMake.getSource().x + "," + moveToMake.getSource().y + "," + " to " + moveToMake.getDestination().x + "," + moveToMake.getDestination().y);
		System.out.println();
		//
		
		// check if moving a Pawn
		Piece movedPiece = boardManager.getPiece(moveToMake.getDestination().x, moveToMake.getDestination().y); 
		if (movedPiece instanceof Pawn) {
			
			// update that first move has already been made
			((Pawn)(movedPiece)).updateFirstMove();
			
			// check if Pawn has reached board end
			if (boardManager.isPieceAtEnd(movedPiece, moveToMake.getDestination())) {
	
				// refresh board display
				displayManager.refreshBoardDisplay();
				
				// remove Pawn
				boardManager.removePiece(moveToMake.getDestination(), isFirstPlayerTurn);
				
				// create replacing piece
				boardManager.addPiece(moveToMake.getDestination(), REPLACING_PIECES[DEFAULT_REPLACING_PIECE], isFirstPlayerTurn);
			}
		}
		
		// switch players
		isFirstPlayerTurn = !isFirstPlayerTurn;
		
		// return move
		return moveToMake;
	 }
	 
	 /**
	  * Scans entire board and adds a Creature in each position in probability of 2%.
	  * If current position already holds a Creature and was chosen to be added, its Creature is destroyed 
	  */
	@Override
	public List<Point> placeCreatures() {
		 
		Point currPosition = new Point();
		 
		// scan entire board
		for (int row = 1 ; row <= boardSize ; ++row) {
			for (int col = 1 ; col <= boardSize ; ++col) {
				 
			// check if current position is free
			if (boardManager.getPiece(row, col) != null) {
				// move to next position
				continue;
			}

			// add a Creature in probability of 2%  
			if (randomGenerator.nextInt(100) < CHANCE_FOR_CREATURE) { // TODO check calculation
					 
				// check if there is already another Creature at current position
				currPosition.move(row, col);
				if (creaturePositions.contains(currPosition)) {
						 
					// destroy creature
					creaturePositions.remove(currPosition);
						 
					// TODO DEBUG!!!
					// write to log that creatures collide
					logger.writeCollide(currPosition);
					// print log to screen
					System.out.println();
					System.out.println("log: " + "l " + currPosition.x + "," + currPosition.y);
					System.out.println();
					//
						 
					// move to next position
						continue;
					}
					 
					// add a new Creature
					creaturePositions.add(new Point(currPosition));
					 					 
					// TODO DEBUG!!!
					// write to log that current piece is destroyed
					logger.writeCreatur(currPosition);
					// print log to screen
					System.out.println();
					System.out.println("log: " + "c " + currPosition.x + "," + currPosition.y);
					System.out.println();
					//
				}
			}
		}
		 
		// return Creatures' positions
		return creaturePositions;
	}
	 
	/**
	 * Scans entire board and adds a Creature in each position in probability of 2%.
	 * After that, 
	 */
	
	/**
	 * Chooses a piece and one possible move.
	 * Gets all positions on piece's path. If one of this positions hold a Creature, piece is destroyed,
	 * move is not done and destroyer-Creature position is updated.
	 * Otherwise (no Creatures on the path), move is made.
	 */
	@Override
	public Point[] makeMoveComputerSpecialGame() {
		 
		// choose a move
		Move move = chooseMove();

		// initialize an empty path
		List<Point> path = new LinkedList<Point>();
		 
		// default is that no Creature has destroyed a piece
		Point destroyerCreature = null;
		 
		// check if chosen piece is a Knight
		if (boardManager.getPiece(move.getSource().x, move.getSource().y) instanceof Knight) {
			 
			moveKnightSpecialGame(path, move);
			 
		} else if (boardManager.getPiece(move.getSource().x, move.getSource().y) instanceof King) {
			 
			moveKingSpecialGame(path, move);
			 
		} else {
			 
			// get path
			path.addAll(getPath(move.getSource(), move.getDestination()));
			 
			boolean removedPiece = false; 
			 
			// check if passes through Creatures			 
			for (Point currPositionOnPath : path) {

				if (!removedPiece) { 
				 
					for (Point currCreature : creaturePositions) {
						 
						if (currCreature.equals(currPositionOnPath)) {
							 
							// get position of Creature that destroys the piece
							destroyerCreature = currCreature;
							 
							//						 
							System.out.println("piece destroyed");
							
							// indicate that removing current piece
							removedPiece = true;
							 
							System.out.println();
							System.out.println("current on creature list: " + currCreature + " current on path: " + currPositionOnPath + " source: " + move.getSource());
							//
							
							// TODO DEBUG!!!
							// write to log that current piece is destroyed
							logger.writePieceDestroyed(move.getSource());
							// print log to screen
							System.out.println();
							System.out.println("log: " + "d " + move.getSource().x + "," + move.getSource().y);
							System.out.println();
							//
							 
							// TODO
							if (boardManager.getPiece(move.getSource().x, move.getSource().y) == null || move == null) {
								System.out.println("null pointer exception!!!!!!!!");
							}
							//
							 
							// remove piece
							boardManager.removePiece(move.getSource(), boardManager.getPiece(move.getSource().x, move.getSource().y).getPlayer());
							break;
						}
						 
					}
				 
				}
			}
			
			// make the move only if the piece was not removed
			if (!removedPiece) {
				 
				// TODO DEBUG!!!
				// write to log that current piece is destroyed
				logger.writePieceDestroyed(move.getSource());
				// print log to screen
				System.out.println();
				System.out.println("log: " + "m " + move.getSource().x + "," + move.getSource().y + "," + " to " + move.getDestination().x + "," + move.getDestination().y);
				System.out.println();
				//
				 
				// no Creatures in path, make move
				boardManager.movePiece(isFirstPlayerTurn, move.getSource(), move.getDestination());
			}
		}

		// switch players
		isFirstPlayerTurn = !isFirstPlayerTurn;
		
		// return source and destination positions and position of destroyer Creature
		Point[] retVal = {move.getSource(), move.getDestination(), destroyerCreature}; // TODO variable name 
		return retVal;
	}

	/**
	 * Uses the hazard-checker object to determine if game is in Check.
	 * Check is determined according to currently playing player 
	 */
	@Override
	public boolean isCheck() { return hazardChecker.isCheck(isFirstPlayerTurn); } // TODO match comment to "hazard-checker" 's name
	
	/**
	 * Use the hazard-checker object to determine if game is in Stalemate.
	 * Stalemate is determined according to currently playing player
	 */
	@Override
	public boolean isStalemate() { return hazardChecker.isStalemate(isFirstPlayerTurn); } // TODO match comment to "hazard-checker" 's name
	
	/**
	 * Uses the hazard-checker object to determine if game is in Checkmate.
	 * Checkmate is determined according to currently playing player 
	 */
	@Override
	public boolean isCheckmate() { return hazardChecker.isCheckmate(isFirstPlayerTurn); } // TODO match comment to "hazard-checker" 's name
}